// import/export got a false positive, and affects most of our index barrel files
// can be removed once following issue is fixed: https://github.com/import-js/eslint-plugin-import/issues/703
/* eslint-disable import/export */
import { context } from '@opentelemetry/api';
import {
  applySdkMetadata,
  type EventProcessor,
  getCapturedScopesOnSpan,
  getCurrentScope,
  getGlobalScope,
  getIsolationScope,
  getRootSpan,
  GLOBAL_OBJ,
  registerSpanErrorInstrumentation,
  SEMANTIC_ATTRIBUTE_SENTRY_OP,
  SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
  SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
  setCapturedScopesOnSpan,
  spanToJSON,
  stripUrlQueryAndFragment,
} from '@sentry/core';
import { getScopesFromContext } from '@sentry/opentelemetry';
import type { VercelEdgeOptions } from '@sentry/vercel-edge';
import { getDefaultIntegrations, init as vercelEdgeInit } from '@sentry/vercel-edge';
import { TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION } from '../common/span-attributes-with-logic-attached';
import { addHeadersAsAttributes } from '../common/utils/addHeadersAsAttributes';
import { dropMiddlewareTunnelRequests } from '../common/utils/dropMiddlewareTunnelRequests';
import { isBuild } from '../common/utils/isBuild';
import { flushSafelyWithTimeout, waitUntil } from '../common/utils/responseEnd';
import { setUrlProcessingMetadata } from '../common/utils/setUrlProcessingMetadata';
import { distDirRewriteFramesIntegration } from './distDirRewriteFramesIntegration';

export * from '@sentry/vercel-edge';
export * from '../common';
export { captureUnderscoreErrorException } from '../common/pages-router-instrumentation/_error';

// Override core span methods with Next.js-specific implementations that support Cache Components
export { startSpan, startSpanManual, startInactiveSpan } from '../common/utils/nextSpan';
export { wrapApiHandlerWithSentry } from './wrapApiHandlerWithSentry';

export type EdgeOptions = VercelEdgeOptions;

const globalWithInjectedValues = GLOBAL_OBJ as typeof GLOBAL_OBJ & {
  _sentryRewriteFramesDistDir?: string;
  _sentryRelease?: string;
  _sentryRewritesTunnelPath?: string;
};

/** Inits the Sentry NextJS SDK on the Edge Runtime. */
export function init(options: VercelEdgeOptions = {}): void {
  registerSpanErrorInstrumentation();

  if (isBuild()) {
    return;
  }

  const customDefaultIntegrations = getDefaultIntegrations(options);

  // This value is injected at build time, based on the output directory specified in the build config. Though a default
  // is set there, we set it here as well, just in case something has gone wrong with the injection.
  const distDirName = process.env._sentryRewriteFramesDistDir || globalWithInjectedValues._sentryRewriteFramesDistDir;

  if (distDirName) {
    customDefaultIntegrations.push(distDirRewriteFramesIntegration({ distDirName }));
  }

  const opts = {
    defaultIntegrations: customDefaultIntegrations,
    release: process.env._sentryRelease || globalWithInjectedValues._sentryRelease,
    ...options,
  };

  applySdkMetadata(opts, 'nextjs', ['nextjs', 'vercel-edge']);

  const client = vercelEdgeInit(opts);

  client?.on('spanStart', span => {
    const spanAttributes = spanToJSON(span).data;
    const rootSpan = getRootSpan(span);
    const isRootSpan = span === rootSpan;

    dropMiddlewareTunnelRequests(span, spanAttributes);

    // Mark all spans generated by Next.js as 'auto'
    if (spanAttributes?.['next.span_type'] !== undefined) {
      span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, 'auto');
    }

    // Make sure middleware spans get the right op
    if (spanAttributes?.['next.span_type'] === 'Middleware.execute') {
      span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, 'http.server.middleware');
      span.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, 'url');

      if (isRootSpan) {
        // Fork isolation scope for middleware requests
        const scopes = getCapturedScopesOnSpan(span);
        const isolationScope = (scopes.isolationScope || getIsolationScope()).clone();
        const scope = scopes.scope || getCurrentScope();
        const currentScopesPointer = getScopesFromContext(context.active());
        if (currentScopesPointer) {
          currentScopesPointer.isolationScope = isolationScope;
        }

        setCapturedScopesOnSpan(span, scope, isolationScope);
      }
    }

    if (isRootSpan) {
      // todo: check if we can set request headers for edge on sdkProcessingMetadata
      const headers = getIsolationScope().getScopeData().sdkProcessingMetadata?.normalizedRequest?.headers;
      addHeadersAsAttributes(headers, rootSpan);
    }
  });

  // Use the preprocessEvent hook instead of an event processor, so that the users event processors receive the most
  // up-to-date value, but also so that the logic that detects changes to the transaction names to set the source to
  // "custom", doesn't trigger.
  client?.on('preprocessEvent', event => {
    // The otel auto inference will clobber the transaction name because the span has an http.target
    if (
      event.type === 'transaction' &&
      event.contexts?.trace?.data?.['next.span_type'] === 'Middleware.execute' &&
      event.contexts?.trace?.data?.['next.span_name']
    ) {
      if (event.transaction) {
        // Older nextjs versions pass the full url appended to the middleware name, which results in high cardinality transaction names.
        // We want to remove the url from the name here.
        const spanName = event.contexts.trace.data['next.span_name'];

        if (typeof spanName === 'string') {
          const match = spanName.match(/^middleware (GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)/);
          if (match) {
            const normalizedName = `middleware ${match[1]}`;
            event.transaction = normalizedName;
          } else {
            event.transaction = stripUrlQueryAndFragment(event.contexts.trace.data['next.span_name']);
          }
        }
      }
    }

    setUrlProcessingMetadata(event);
  });

  client?.on('spanEnd', span => {
    if (span === getRootSpan(span)) {
      waitUntil(flushSafelyWithTimeout());
    }
  });

  getGlobalScope().addEventProcessor(
    Object.assign(
      (event => {
        // Filter transactions that we explicitly want to drop.
        if (event.type === 'transaction') {
          if (event.contexts?.trace?.data?.[TRANSACTION_ATTR_SHOULD_DROP_TRANSACTION]) {
            return null;
          }

          return event;
        } else {
          return event;
        }
      }) satisfies EventProcessor,
      { id: 'NextLowQualityTransactionsFilter' },
    ),
  );

  try {
    // @ts-expect-error `process.turbopack` is a magic string that will be replaced by Next.js
    if (process.turbopack) {
      getGlobalScope().setTag('turbopack', true);
    }
  } catch {
    // Noop
    // The statement above can throw because process is not defined on the client
  }
}

/**
 * Just a passthrough in case this is imported from the client.
 */
export function withSentryConfig<T>(exportedUserNextConfig: T): T {
  return exportedUserNextConfig;
}
